#ifndef _MOE_MOEA
#define _MOE_MOEA

#include "engine/Crossover.hh"
#include "engine/Selection.hh"
#include "engine/Migration.hh"
#include "engine/Population.hh"
#include "engine/Mutation.hh"

#include "engine/ArrayList.hh"

#include "engine/Properties.hh"
#include "engine/Observable.hh"

/** Base class for all the Multiobjective Evolutionary Algorithms*/
class MOEA : public Observable {

protected:

    Population * current;

    /* operators */
    ArrayList<Crossover> *crossovers;
    ArrayList<Migration> *migrations;
    ArrayList<Selection> *selections;
    ArrayList<Mutation> *mutations;

    /**generations count*/
    int generations;
    /**max generations*/
    int maxGenerations;
    /**migration frecuency
      *@see Migration::configure()
    */
    int migrationGenerations;

    /* the operators rotate on a round robin scheme,
       these ints points to the current instance of those
    */
    int currentSelect;
    int currentMutation;
    int currentCrossover;
    int currentMigration;


    float crossoverProbability;
public:

    MOEA() : Observable() {

        current = NULL;
        crossovers = NULL;
        migrations = NULL;
        selections = NULL;
        mutations = NULL;

        generations = 0;


        currentSelect = 0;
        currentMutation = 0;
        currentCrossover = 0;
        currentMigration = 0;
    }


    MOEA(Population *current,
         ArrayList<Crossover> *crossovers,
         ArrayList<Migration> *migrations,
         ArrayList<Selection> *selections,
         ArrayList<Mutation> *mutations,
         float crossoverProbability) : Observable() {


        this->current = current;
        this->crossovers = crossovers;
        this->migrations = migrations;
        this->selections = selections;
        this->mutations = mutations;

        this->crossoverProbability = crossoverProbability;

        generations = 0;

        currentSelect = 0;
        currentMutation = 0;
        currentCrossover = 0;
        currentMigration = 0;
    }

    virtual ~MOEA() {
        delete current;
        delete crossovers;
        delete migrations;
        delete selections;
        delete mutations;
    }

    Population *getCurrent() {
        return current;
    }

    void setCurrent(Population *population) {
        this->current = population;
    }

    void setCrossovers(ArrayList<Crossover> *crossovers) {
        this->crossovers = crossovers;
    }

    ArrayList<Crossover> *getCrossovers() {
        return crossovers;
    }

    void setCrossoverProbability(float crossoverProbability) {
        this->crossoverProbability = crossoverProbability;
    }

    float getCrossoverProbability() {
        return crossoverProbability;
    }

    void setMigrations(ArrayList<Migration> *migrations) {
        this->migrations = migrations;
    }

    ArrayList<Migration> *getMigrations() {
        return migrations;
    }

    void setSelections(ArrayList<Selection> *selections) {
        this->selections = selections;
    }

    ArrayList<Selection> *getSelections() {
        return selections;
    }

    void setMutations(ArrayList<Mutation> *mutations) {
        this->mutations = mutations;
    }

    ArrayList<Mutation> *getMutations() {
        return mutations;
    }

    int getMaxGenerations() {
        return maxGenerations;
    }

    void setMaxGenerations(int maxGenerations) {
        this->maxGenerations = maxGenerations;
    }

    int getMigrationGenerations() {
        return migrationGenerations;
    }

    void setMigrationGenerations(int migrationGenerations) {
        this->migrationGenerations = migrationGenerations;
    }

    /**start the algorithm, note that every generation send the notify message to the observers*/
    virtual void run() {

        initialize();

        while (!stopCondition()) {
            generation();
            generations++;

            if (migrationCondition()) migrate();

            notifyObservers(this);
        }

        finalize();
    }

    virtual bool stopCondition() {
        return generations >= getMaxGenerations();
    }

    virtual int getGenerations() {
        return generations;
    }

protected:
    /**one generation code*/
    virtual void generation() = 0;

    virtual void migrate() {

        if (migrations == NULL || migrations->length() == 0) return ;

        else nextMigration()->migrate(getCurrent());

    }

    /**the code in this method get called when the algorithm is over*/
    virtual void finalize() {
        notifyObservers(this);
    }

    /**the code in this method get called when the algorithm begins*/
    virtual void initialize() {
        notifyObservers(this);
    }

    /*the methods bellow implements the round robin scheme, in order to rotate operators*/
    virtual Selection *nextSelect() {
        currentSelect = (currentSelect + 1) % selections->length();
        return selections->get(currentSelect);
    }

    virtual Mutation *nextMutation() {
        currentMutation = (currentMutation + 1) % mutations->length();
        return mutations->get(currentMutation);
    }

    virtual Crossover *nextCrossover() {
        currentCrossover = (currentCrossover + 1) % crossovers->length();
        return crossovers->get(currentCrossover);
    }

    virtual Migration *nextMigration() {

        if (migrations->length() == 0) return NULL;

        currentMigration = (currentMigration + 1) % migrations->length();

        return migrations->get(currentMigration);
    }

    virtual bool migrationCondition() {

        if (generations > 0) return (generations % getMigrationGenerations()) == 0;

        else return false;
    }
};

#endif
